#include "spu.h"

void *client_fun(void *arg)
{
    int connfd = (long int)arg;
    printf("I am the new client %d\n", connfd);
    char buff[100];  //接受客户端数据缓冲区
    memset(buff, 0, sizeof(buff));
    while(1){
        int recv = read(connfd, buff, sizeof(buff));
        if(recv > 0){
            printf("I am %d, recieve data = %s\n", connfd, buff);
            memset(buff, 0, sizeof(buff));
        }
        //如果返回是0，说明链接可能断开
        if(recv == 0){
            printf("I am %d, closed\n", connfd);
            close(connfd);  //关闭链接
            pthread_exit(NULL);
        }
    }   
}

int main()
{
    /*  1.创建套接字  
        int socket(int domain, int type, int protocol);

        参数1：domain
            协议簇（例如下）：
                AF_INET：IPV4
                AF_INET6：IPV6
        参数2：type
            套接字类型，即通信语义（例如下）：
                SOCK_STREAM：提供有序、可靠、双向、基于连接的字节流。 （TCP）
                SOCK_DGRAM：支持数据报（固定最大值的无连接、不可靠的消息长度） （UDP）
        参数3：protocol
            指定特定协议：通常，在给定的协议族中，只有一个协议支持特定的套接字类型，
                        在这种情况下，协议可以指定为0
        返回值（新套接字的文件描述符）：
            成功后，将返回新套接字的文件描述符。出现错误时，将返回-1，并适当设置errno。
    */
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1){
        printf("create socket failed!\n");
        return 0;
    }

    /*  2.绑定IP和端口,分配地址给套接字
        int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

        参数1：sockfd
            文件描述符：socket() 创建的新套接字的文件描述符
        参数2：addr (ipv4协议对应的是 struct sockaddr_in 结构体)
            地址：该地址被分配给文件描述符sockfd引用的套接字
        参数3：addrlen
            addr指向的地址结构的大小（以字节为单位）
        返回值：
            成功后，返回0。出现错误时，将返回-1，并适当设置errno。

        *** IPV4 使用 struct sockaddr_in 类型的去为socket指定协议地址 ***
        struct sockaddr_in {
            sa_family_t    sin_family; // address family: AF_INET
            in_port_t      sin_port;   // port in network byte order 
            struct in_addr sin_addr;   // internet address 
        };
    */
    struct sockaddr_in addr;
    addr.sin_port = htons(6666);  //函数将无符号短整数hostshort从主机字节顺序转换为网络字节顺序。
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY:0.0.0.0，可以泛指本机的所有IP（多个网卡）
                                              //而127.0.0.1只能是服务器和客户端都在本机上才可以
    int res;
    res = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(res == -1){
        printf("bind failed!\n");
        return 0;
    }

    /*  3.监听链接请求
        int listen(int sockfd, int backlog);

        参数1：sockfd
            监听的套接字文件描述符
        参数2：backlog
            定义挂起连接队列的最大长度
        返回值：
            成功后，返回0。出现错误时，将返回-1，并适当设置errno。
    */
    if(listen(sockfd, 100) == -1){
        printf("listen failed!\n");
        return 0;
    }
    printf("start accept\n");
    /*  4.循环等待客户端链接
        int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

        参数1：sockfd
            套接字文件描述符
        参数2：addr 
            NULL
        参数3：addrlen
            NULL
        返回值：
            成功：一个非负整数，即已连接socket文件描述符，用于和客户端通信
                 原先的socket描述符继续用于监听
            失败：-1
    */ 
    /*主线程接收客户端链接请求*/
    while(1){
        int connfd;
        connfd = accept(sockfd, NULL, NULL);
        if(connfd != -1){ 
            printf("a client connect!, connfd = %d\n",connfd);
            /*创建新线程处理客户端数据*/
            pthread_t thread_id;
            pthread_create(&thread_id, NULL, client_fun, (void*)connfd);
        }
    }
    return 0;
}
